package cn.pilipa.common.config;

import com.mongodb.MongoClient;
import java.time.LocalDate;
import java.time.YearMonth;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.bson.Document;
import org.javamoney.moneta.Money;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;

@Configuration
@ConditionalOnClass(MongoClient.class)
public class MongoConfig {

  @Configuration
  @ConditionalOnClass(Money.class)
  public static class MoneyMongoConfig {
    @Bean
    public Converter moneyReadConverter() {
      return new MoneyReadConverter();
    }

    @Bean
    public Converter moneyWriteConverter() {
      return new MoneyWriteConverter();
    }

  }

  /**
   * mongo转换配置.
   */
  @Bean
  public MongoCustomConversions customConversionsWithMoney(ApplicationContext context) {
    List<Converter> converters = new ArrayList<>(javaTimeConverters());
    Map<String, Converter> converterMap = context.getBeansOfType(Converter.class);
    converters.addAll(converterMap.values());
    return new MongoCustomConversions(converters);
  }

  private List<Converter<?, ?>> javaTimeConverters() {
    return Arrays.asList(
      new LocalDateWriter(),
      new LocalDateReader(),
      new YearMonthWriter(),
      new YearMonthReader()
    );
  }

  /**
   * 转换器.
   */
  @Bean
  public MappingMongoConverter mongoConverter(
        MongoDbFactory mongoDbFactory,
        MongoMappingContext mongoMappingContext,
        MongoCustomConversions customConversions) throws Exception {
    DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory);
    MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
    converter.setCustomConversions(customConversions);
    converter.afterPropertiesSet();
    return converter;
  }

  static class MoneyReadConverter implements Converter<Document, Money> {
    @Override
    public Money convert(Document source) {
      return Money.of(
        Double.valueOf((String) source.get("value")),
        (String) source.get("currency")
      );
    }
  }

  static class MoneyWriteConverter implements Converter<Money, Document> {
    @Override
    public Document convert(Money source) {
      Document dbo = new Document();
      dbo.put("value", source.getNumber().toString());
      dbo.put("currency", source.getCurrency().getCurrencyCode());
      dbo.put("_class", Money.class.getName());
      return dbo;
    }
  }

  static class LocalDateWriter implements Converter<LocalDate, String> {
    @Override
    public String convert(LocalDate source) {
      return source.toString();
    }
  }

  static class LocalDateReader implements Converter<String, LocalDate> {
    @Override
    public LocalDate convert(String source) {
      return LocalDate.parse(source);
    }
  }

  static class YearMonthWriter implements Converter<YearMonth, String> {
    @Override
    public String convert(YearMonth source) {
      return source.toString();
    }
  }

  static class YearMonthReader implements Converter<String, YearMonth> {
    @Override
    public YearMonth convert(String source) {
      return YearMonth.parse(source);
    }
  }

}
